#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <assert.h>

#define RFID_NUMBYTES 10
#define PLAYER_NAMELEN 15
#define EEPROM_SIZE 4096
#define NUM_PLAYERS 28 // EEPROM_SIZE = n_players * 28 + n_players**2 * 4

#define PLAYERS_OFFSET 0
#define RESULTS_OFFSET (PLAYERS_OFFSET + NUM_PLAYERS*sizeof(struct player))
// RESULT_OFFSET(p1,p2) contains the number of games p1 won against p2
#define RESULT_OFFSET(id1,id2) (RESULTS_OFFSET + (id1 * (NUM_PLAYERS-1) + id2)*sizeof(result))
#define PLAYER_OFFSET(i) (PLAYERS_OFFSET + i*sizeof(struct player))

typedef char byte;
typedef unsigned size_t;
typedef signed ssize_t;

struct player {
    byte id;
    byte rfid[RFID_NUMBYTES];
    char name[PLAYER_NAMELEN];
    short games_played;
    short games_won;
    short games_lost;
    short total_score;
} __attribute__((__packed__));

typedef short result;

byte eeprom[EEPROM_SIZE];

void eeprom_zero() {
    memset(eeprom, 0, sizeof(eeprom));
}

byte eeprom_read(int offset) {
    assert(offset>=0 && offset<EEPROM_SIZE);
    int res=eeprom[offset];
    printf("eeprom_read(%u)=0x%x\n", offset, res);
    return res;
}
void eeprom_write(int offset, byte val) {
    printf("eeprom_write(offset=%u, val=0x%x)\n", offset, val);
    assert(offset>=0 && offset<EEPROM_SIZE);
    eeprom[offset] = val;
}

// --------------------

// shit, 8 makes it platform dependent! OH GNOES!
byte cache_free_slot[NUM_PLAYERS/8+1];
byte cache_free_id[NUM_PLAYERS/8+1];

#define BITMAP_GET(bitmap, x) (bitmap[x/8] & 1<<(x%8))
#define BITMAP_CLEAR(bitmap, x) do {bitmap[x/8] &= ~(1<<(x%8));} while(0)
#define BITMAP_SET(bitmap, x) do { bitmap[x/8] |= (1<<(x%8));} while(0)
// warning: dont use the BITMAP_NEXT_FREE on pointers or it will break horribly

#define RESERVE_FREE_SLOT() (_bitmap_reserve_next_free(cache_free_slot, NUM_PLAYERS))
#define RESERVE_FREE_ID() (_bitmap_reserve_next_free(cache_free_id, NUM_PLAYERS))

int _bitmap_reserve_next_free(byte *bitmap, int max) {
    int i;
    for (i=0;i<max;i+=8) {
        if (bitmap[i/8]) { // any bit set in that byte
            for(;i<max;i++) {
                if (BITMAP_GET(bitmap, i)) {
                    BITMAP_CLEAR(bitmap, i);
                    return i;
                }
            }
            assert(0);
        }
    }
    return -1;
}

void init_cache() {
    int i;
    byte current_id;
    memset(cache_free_slot, 0, sizeof(cache_free_slot));
    memset(cache_free_id, 0xff, sizeof(cache_free_id));
    for (i=0;i<NUM_PLAYERS;i++) {
        current_id = eeprom_read(PLAYER_OFFSET(i) + offsetof(struct player, id));
        if (current_id==0) {
            BITMAP_SET(cache_free_slot, i);
        } else {
            BITMAP_CLEAR(cache_free_id, current_id);
        }
    }
}


// --------------------



void eeprom_pread(void *buf, ssize_t count, ssize_t offset) {
    int i;
    printf("eeprom_pread(buf, count=%u, offset=%u)\n", count, offset);
    assert(offset+count <= EEPROM_SIZE);
    for (i=0;i<count;i++)
        ((byte*)buf)[i] = eeprom_read(offset+i);
}

void eeprom_pwrite(void *buf, ssize_t count, ssize_t offset) {
    int i;
    printf("eeprom_pwrite(buf, count=%u, offset=%u)\n", count, offset);
    assert(offset+count <= EEPROM_SIZE);
    for (i=0;i<count;i++)
        eeprom_write(offset+i, ((byte*)buf)[i]);
}

void eeprom_memset(ssize_t offset, byte c, size_t n) {
    int i;
    printf("eeprom_memset(offset=%u, c=0x%x, n=%u)\n", offset, c, n);
    assert(offset+n <= EEPROM_SIZE);
    for (i=0;i<n;i++)
        eeprom_write(offset+i, c);
}

// --------------------

//eeprom_pread(&currPlayer, sizeof(player), i * sizeof(player));
//

int player_by_rfid(char *rfid) { // returns player offset
    int i;
    byte current_rfid[RFID_NUMBYTES];
    for (i=0;i<NUM_PLAYERS;i++) {
        eeprom_pread(current_rfid, RFID_NUMBYTES, PLAYER_OFFSET(i)+offsetof(struct player, rfid));
        if (memcmp(current_rfid, rfid, sizeof(current_rfid))==0) {
            return PLAYER_OFFSET(i);
        }
    }
    return -1;
}
#define min(a,b) ({typeof(a) _a=(a); typeof(b) _b=(b); _b<_a?_b:_a;})


int player_create(char *rfid, char *name) { // return player offset or -1 if it fails
    struct player player;
    int slot;

    memset(&player, 0, sizeof(player));

    printf("--- reserve_free_id now:\n");
    player.id = RESERVE_FREE_ID();
    if (player.id==-1) {
        return -1;
    }
    printf("--- reserve_free_slot now:\n");
    slot = RESERVE_FREE_SLOT(); // if we get a free id, we must get a free slot
    assert(slot!=-1);

    memcpy(player.rfid, rfid, sizeof(player.rfid));
    memcpy(player.name, name, min(strlen(name), sizeof(player.name)));

    eeprom_pwrite(&player, sizeof(player), PLAYER_OFFSET(slot));
    return PLAYER_OFFSET(slot);
}

void player_delete(int offset) {
    int other_id;
    short zero;
    byte player_id = eeprom_read(offset+offsetof(struct player, id));
    for (other_id=0; other_id<NUM_PLAYERS; other_id++) {
        // zero matches of this player
        eeprom_memset(RESULT_OFFSET(player_id, other_id), 0, sizeof(result));
        eeprom_memset(RESULT_OFFSET(other_id, player_id), 0, sizeof(result));
    }
    eeprom_memset(offset, 0, sizeof(struct player));
}

inline void player_readname(int offset, char *target) {
    eeprom_pread(target, PLAYER_NAMELEN, offset+offsetof(struct player, name));
}

result match_get(int offset1, int offset2) {
    result val;
    eeprom_pread(&val, sizeof(val), RESULT_OFFSET(offset1, offset2));
    return val;
}
void match_add(int offset1, int offset2, int wins) {
    result val;
    val = match_get(offset1, offset2) + wins;
    eeprom_pwrite(&val, sizeof(val), RESULT_OFFSET(offset1, offset2));
}

void test_shit() {
    int p1_offset, p2_offset;
    int p1_wins, p2_wins;
    byte p1_id, p2_id;
    char name[PLAYER_NAMELEN+1];
    name[PLAYER_NAMELEN] = 0;

#define P1_RFID "AAABBBCCCD"
#define P2_RFID "AAABBBCCCE"
#define P1_NAME "fnord"
#define P2_NAME "AAAAAAAAAAAAAAAAAAAAAAABBB"

    p1_offset=player_create(P1_RFID, P1_NAME);
    printf("p1_offset=%d\n", p1_offset);
    p2_offset=player_create(P2_RFID, P2_NAME);
    printf("p2_offset=%d\n", p2_offset);
    assert(p1_offset!=-1);
    assert(p2_offset!=-1);
    assert(p1_offset!=p2_offset);
    player_readname(p1_offset, name);
    printf("p1 name from eeprom: %s\n", name);
    player_readname(p2_offset, name);
    printf("p2 name from eeprom: %s\n", name);

    p1_id = eeprom_read(p1_offset + offsetof(struct player, id));
    p2_id = eeprom_read(p2_offset + offsetof(struct player, id));
    match_add(p1_id, p2_id, 1);
    match_add(p1_id, p2_id, 2);
    match_add(p2_id, p1_id, 5);
    p1_wins = match_get(p1_id, p2_id);
    p2_wins = match_get(p2_id, p1_id);
    printf("p1 vs p2: %d : %d\n", p1_wins, p2_wins);
    assert(p1_wins == 3);
    assert(p2_wins == 5);
    match_add(p2_id, p1_id, 23415);
    p2_wins = match_get(p2_id, p1_id);
    assert(p2_wins == 23420);

    assert(player_by_rfid(P1_RFID) == p1_offset);
    assert(player_by_rfid(P2_RFID) == p2_offset);
    assert(player_by_rfid("XXXXYYYYXX") == -1);
    player_delete(p1_offset);
    assert(player_by_rfid(P1_RFID) == -1);
    p2_wins = match_get(p2_id, p1_id);
    p1_wins = match_get(p1_id, p2_id);
    assert(p1_wins==0);
    assert(p2_wins==0);

}

int main() {
    int bytes_needed = sizeof(struct player)*NUM_PLAYERS + NUM_PLAYERS*NUM_PLAYERS * 2 * sizeof(result);
    eeprom_zero();
    init_cache();
    printf("p: %u\n", sizeof(struct player));
    printf("r: %u\n", sizeof(result));
    printf("bytes needed: %u\n", bytes_needed);
    assert(bytes_needed < EEPROM_SIZE);

    test_shit();
    printf("success!\n");
    return 0;
}



